/*
** String handling.
** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in luajit.h
*/

#define lj_str_c
#define LUA_CORE

#include "lj_obj.h"
#include "lj_gc.h"
#include "lj_err.h"
#include "lj_str.h"
#include "lj_char.h"
#include "lj_prng.h"
#include "lj_codecache.h"
#include "lua.h"
#include "lauxlib.h"
#include <stdio.h>
#include <time.h>

static void lj_str_pool_remove(SharedStrData *target_data);

/* -- String helpers ------------------------------------------------------ */

/* Ordered compare of strings. Assumes string data is 4-byte aligned. */
int32_t LJ_FASTCALL lj_str_cmp(GCstr *a, GCstr *b)
{
  MSize i, n = a->len > b->len ? b->len : a->len;
#ifdef LUAJIT_USE_VALGRIND
  for (i = 0; i < n; i++) {
    uint8_t va = *(const uint8_t *)(strdata(a)+i);
    uint8_t vb = *(const uint8_t *)(strdata(b)+i);
    if (va != vb) {
        return va < vb ? -1 : 1;
    }
  }
#else
  for (i = 0; i < n; i += 4) {
    /* Note: innocuous access up to end of string + 3. */
    uint32_t va = *(const uint32_t *)(strdata(a)+i);
    uint32_t vb = *(const uint32_t *)(strdata(b)+i);
    if (va != vb) {
#if LJ_LE
      va = lj_bswap(va); vb = lj_bswap(vb);
#endif
      i -= n;
      if ((int32_t)i >= -3) {
	va >>= 32+(i<<3); vb >>= 32+(i<<3);
	if (va == vb) break;
      }
      return va < vb ? -1 : 1;
    }
  }
#endif
  return (int32_t)(a->len - b->len);
}

/* Find fixed string p inside string s. */
const char *lj_str_find(const char *s, const char *p, MSize slen, MSize plen)
{
  if (plen <= slen) {
    if (plen == 0) {
      return s;
    } else {
      int c = *(const uint8_t *)p++;
      plen--; slen -= plen;
      while (slen) {
	const char *q = (const char *)memchr(s, c, slen);
	if (!q) break;
	if (memcmp(q+1, p, plen) == 0) return q;
	q++; slen -= (MSize)(q-s); s = q;
      }
    }
  }
  return NULL;
}

/* Check whether a string has a pattern matching character. */
int lj_str_haspattern(GCstr *s)
{
  const char *p = strdata(s), *q = p + s->len;
  while (p < q) {
    int c = *(const uint8_t *)p++;
    if (lj_char_ispunct(c) && strchr("^$*+?.([%-", c))
      return 1;  /* Found a pattern matching char. */
  }
  return 0;  /* No pattern matching chars found. */
}

/* -- String hashing ------------------------------------------------------ */

#ifdef LJ_HAS_OPTIMISED_HASH
static StrHash hash_sparse_def (uint64_t, const char *, MSize);
str_sparse_hashfn hash_sparse = hash_sparse_def;
#if LUAJIT_SECURITY_STRHASH
static StrHash hash_dense_def(uint64_t, StrHash, const char *, MSize);
str_dense_hashfn hash_dense = hash_dense_def;
#endif
#else
#define hash_sparse hash_sparse_def
#if LUAJIT_SECURITY_STRHASH
#define hash_dense hash_dense_def
#endif
#endif

/* Keyed sparse ARX string hash. Constant time. */
static StrHash hash_sparse_def(uint64_t seed, const char *str, MSize len)
{
  /* Constants taken from lookup3 hash by Bob Jenkins. */
  StrHash a, b, h = len ^ (StrHash)seed;
  if (len >= 4) {  /* Caveat: unaligned access! */
    a = lj_getu32(str);
    h ^= lj_getu32(str+len-4);
    b = lj_getu32(str+(len>>1)-2);
    h ^= b; h -= lj_rol(b, 14);
    b += lj_getu32(str+(len>>2)-1);
  } else {
    a = *(const uint8_t *)str;
    h ^= *(const uint8_t *)(str+len-1);
    b = *(const uint8_t *)(str+(len>>1));
    h ^= b; h -= lj_rol(b, 14);
  }
  a ^= h; a -= lj_rol(h, 11);
  b ^= a; b -= lj_rol(a, 25);
  h ^= b; h -= lj_rol(b, 16);
  return h;
}

#if LUAJIT_SECURITY_STRHASH
/* Keyed dense ARX string hash. Linear time. */
static LJ_NOINLINE StrHash hash_dense_def(uint64_t seed, StrHash h,
					  const char *str, MSize len)
{
  StrHash b = lj_bswap(lj_rol(h ^ (StrHash)(seed >> 32), 4));
  if (len > 12) {
    StrHash a = (StrHash)seed;
    const char *pe = str+len-12, *p = pe, *q = str;
    do {
      a += lj_getu32(p);
      b += lj_getu32(p+4);
      h += lj_getu32(p+8);
      p = q; q += 12;
      h ^= b; h -= lj_rol(b, 14);
      a ^= h; a -= lj_rol(h, 11);
      b ^= a; b -= lj_rol(a, 25);
    } while (p < pe);
    h ^= b; h -= lj_rol(b, 16);
    a ^= h; a -= lj_rol(h, 4);
    b ^= a; b -= lj_rol(a, 14);
  }
  return b;
}
#endif

/* -- String interning ---------------------------------------------------- */

#define LJ_STR_MAXCOLL		32

/* Resize the string interning hash table (grow and shrink). */
void lj_str_resize(lua_State *L, MSize newmask)
{
  global_State *g = G(L);
  GCRef *newtab, *oldtab = g->str.tab;
  MSize i;

  /* No resizing during GC traversal or if already too big. */
  if (g->gc.state == GCSsweepstring || newmask >= LJ_MAX_STRTAB-1)
    return;

  newtab = lj_mem_newvec(L, newmask+1, GCRef);
  memset(newtab, 0, (newmask+1)*sizeof(GCRef));

#if LUAJIT_SECURITY_STRHASH
  /* Check which chains need secondary hashes. */
  if (g->str.second) {
    int newsecond = 0;
    /* Compute primary chain lengths. */
    for (i = g->str.mask; i != ~(MSize)0; i--) {
      GCobj *o = (GCobj *)(gcrefu(oldtab[i]) & ~(uintptr_t)1);
      while (o) {
	GCstr *s = gco2str(o);
	MSize hash = s->hashalg ? hash_sparse(g->str.seed, strdata(s), s->len) :
				  s->hash;
	hash &= newmask;
	setgcrefp(newtab[hash], gcrefu(newtab[hash]) + 1);
	o = gcnext(o);
      }
    }
    /* Mark secondary chains. */
    for (i = newmask; i != ~(MSize)0; i--) {
      int secondary = gcrefu(newtab[i]) > LJ_STR_MAXCOLL;
      newsecond |= secondary;
      setgcrefp(newtab[i], secondary);
    }
    g->str.second = newsecond;
  }
#endif

  /* Reinsert all strings from the old table into the new table. */
  for (i = g->str.mask; i != ~(MSize)0; i--) {
    GCobj *o = (GCobj *)(gcrefu(oldtab[i]) & ~(uintptr_t)1);
    while (o) {
      GCobj *next = gcnext(o);
      GCstr *s = gco2str(o);
      MSize hash = s->hash;
#if LUAJIT_SECURITY_STRHASH
      uintptr_t u;
      if (LJ_LIKELY(!s->hashalg)) {  /* String hashed with primary hash. */
	hash &= newmask;
	u = gcrefu(newtab[hash]);
	if (LJ_UNLIKELY(u & 1)) {  /* Switch string to secondary hash. */
	  s->hash = hash = hash_dense(g->str.seed, s->hash, strdata(s), s->len);
	  s->hashalg = 1;
	  hash &= newmask;
	  u = gcrefu(newtab[hash]);
	}
      } else {  /* String hashed with secondary hash. */
	MSize shash = hash_sparse(g->str.seed, strdata(s), s->len);
	u = gcrefu(newtab[shash & newmask]);
	if (u & 1) {
	  hash &= newmask;
	  u = gcrefu(newtab[hash]);
	} else {  /* Revert string back to primary hash. */
	  s->hash = shash;
	  s->hashalg = 0;
	  hash = (shash & newmask);
	}
      }
      /* NOBARRIER: The string table is a GC root. */
      setgcrefp(o->gch.nextgc, (u & ~(uintptr_t)1));
      setgcrefp(newtab[hash], ((uintptr_t)o | (u & 1)));
#else
      hash &= newmask;
      /* NOBARRIER: The string table is a GC root. */
      setgcrefr(o->gch.nextgc, newtab[hash]);
      setgcref(newtab[hash], o);
#endif
      o = next;
    }
  }

  /* Free old table and replace with new table. */
  lj_str_freetab(g);
  g->str.tab = newtab;
  g->str.mask = newmask;
}

#if LUAJIT_SECURITY_STRHASH
/* Rehash and rechain all strings in a chain. */
static LJ_NOINLINE GCstr *lj_str_rehash_chain(lua_State *L, StrHash hashc,
					      const char *str, MSize len)
{
  global_State *g = G(L);
  int ow = g->gc.state == GCSsweepstring ? otherwhite(g) : 0;  /* Sweeping? */
  GCRef *strtab = g->str.tab;
  MSize strmask = g->str.mask;
  GCobj *o = gcref(strtab[hashc & strmask]);
  setgcrefp(strtab[hashc & strmask], (void *)((uintptr_t)1));
  g->str.second = 1;
  while (o) {
    uintptr_t u;
    GCobj *next = gcnext(o);
    GCstr *s = gco2str(o);
    StrHash hash;
    if (ow) {  /* Must sweep while rechaining. */
      if (((o->gch.marked ^ LJ_GC_WHITES) & ow)) {  /* String alive? */
	lj_assertG(!isdead(g, o) || (o->gch.marked & LJ_GC_FIXED),
		   "sweep of undead string");
	makewhite(g, o);
      } else {  /* Free dead string. */
	lj_assertG(isdead(g, o) || ow == LJ_GC_SFIXED,
		   "sweep of unlive string");
	lj_str_free(g, s);
	o = next;
	continue;
      }
    }
    hash = s->hash;
    if (!s->hashalg) {  /* Rehash with secondary hash. */
      hash = hash_dense(g->str.seed, hash, strdata(s), s->len);
      s->hash = hash;
      s->hashalg = 1;
    }
    /* Rechain. */
    hash &= strmask;
    u = gcrefu(strtab[hash]);
    setgcrefp(o->gch.nextgc, (u & ~(uintptr_t)1));
    setgcrefp(strtab[hash], ((uintptr_t)o | (u & 1)));
    o = next;
  }
  /* Try to insert the pending string again. */
  return lj_str_new(L, str, len);
}
#endif

/* Reseed String ID from PRNG after random interval < 2^bits. */
#if LUAJIT_SECURITY_STRID == 1
#define STRID_RESEED_INTERVAL	8
#elif LUAJIT_SECURITY_STRID == 2
#define STRID_RESEED_INTERVAL	4
#elif LUAJIT_SECURITY_STRID >= 3
#define STRID_RESEED_INTERVAL	0
#endif

/* Allocate a new string and add to string interning table. */
static GCstr *lj_str_alloc(lua_State *L, const char *str, MSize len,
			   StrHash hash, int hashalg)
{
  GCstr *s = lj_mem_newt(L, lj_str_size(len), GCstr);
  global_State *g = G(L);
  uintptr_t u;
  newwhite(g, s);
  s->gct = ~LJ_TSTR;
  s->len = len;
  s->hash = hash;

  /* Get shared string data from global pool */
  s->shared_data = lj_str_pool_get(str, len);
  if (!s->shared_data) {
    lj_mem_free(g, s, lj_str_size(len));
    lj_err_mem(L);
  }

  /* Use global unique sid from SharedStrData */
  s->sid = s->shared_data->sid;
  s->reserved = 0;
  s->hashalg = (uint8_t)hashalg;
  /* Add to string hash table. */
  hash &= g->str.mask;
  u = gcrefu(g->str.tab[hash]);
  setgcrefp(s->nextgc, (u & ~(uintptr_t)1));
  /* NOBARRIER: The string table is a GC root. */
  setgcrefp(g->str.tab[hash], ((uintptr_t)s | (u & 1)));
  if (g->str.num++ > g->str.mask)  /* Allow a 100% load factor. */
    lj_str_resize(L, (g->str.mask<<1)+1);  /* Grow string table. */
  return s;  /* Return newly interned string. */
}

/* Intern a string and return string object. */
GCstr *lj_str_new(lua_State *L, const char *str, size_t lenx)
{
  global_State *g = G(L);
  if (lenx-1 < LJ_MAX_STR-1) {
    MSize len = (MSize)lenx;
    StrHash hash = hash_sparse(g->str.seed, str, len);
    MSize coll = 0;
    int hashalg = 0;
    /* Check if the string has already been interned. */
    GCobj *o = gcref(g->str.tab[hash & g->str.mask]);
#if LUAJIT_SECURITY_STRHASH
    if (LJ_UNLIKELY((uintptr_t)o & 1)) {  /* Secondary hash for this chain? */
      hashalg = 1;
      hash = hash_dense(g->str.seed, hash, str, len);
      o = (GCobj *)(gcrefu(g->str.tab[hash & g->str.mask]) & ~(uintptr_t)1);
    }
#endif
    while (o != NULL) {
      GCstr *sx = gco2str(o);
      if (sx->hash == hash && sx->len == len) {
	if (memcmp(str, strdata(sx), len) == 0) {
	  if (isdead(g, o)) flipwhite(o);  /* Resurrect if dead. */
	  return sx;  /* Return existing string. */
	}
	coll++;
      }
      coll++;
      o = gcnext(o);
    }
#if LUAJIT_SECURITY_STRHASH
    /* Rehash chain if there are too many collisions. */
    if (LJ_UNLIKELY(coll > LJ_STR_MAXCOLL) && !hashalg) {
      return lj_str_rehash_chain(L, hash, str, len);
    }
#endif
    /* Otherwise allocate a new string. */
    return lj_str_alloc(L, str, len, hash, hashalg);
  } else {
    if (lenx)
      lj_err_msg(L, LJ_ERR_STROV);
    return &g->strempty;
  }
}

void LJ_FASTCALL lj_str_free(global_State *g, GCstr *s)
{
  g->str.num--;
  if (s->shared_data) {
    uint32_t old_count, new_count;
    do {
      old_count = s->shared_data->refcount;
      if (old_count == 0) {
        return;
      }
      new_count = old_count - 1;
    } while (!__sync_bool_compare_and_swap(&s->shared_data->refcount, old_count, new_count));
    
    if (old_count == 1) {
      lj_str_pool_remove(s->shared_data);
    }
  }
  lj_mem_free(g, s, lj_str_size(s->len));
}

/* -- Shared String Pool --------------------------------------------------- */

/* Simple global string pool using hash table */
static struct {
  SharedStrData **buckets;
  MSize mask;              /* Hash table mask (size - 1) */
  MSize size;              /* Current hash table size */
  MSize count;             /* Current number of strings */
  volatile uint32_t lock;  /* spinlock */
  uint32_t initialized;
  uint64_t seed;
  volatile uint32_t next_sid;  /* Current sid counter */
} str_pool = {0};

#define STR_POOL_MIN_SIZE 1024
#define STR_POOL_MAX_LOAD_FACTOR 0.75  /* Resize when load factor > 75% */

static void str_pool_lock(void) {
  while (__sync_lock_test_and_set(&str_pool.lock, 1)) {
    /* busy wait */
  }
}

static void str_pool_unlock(void) {
  __sync_lock_release(&str_pool.lock);
}

static MSize str_pool_hash(StrHash hash, MSize table_size) {
  hash ^= hash >> 16;
  hash *= 0x85ebca6b;
  hash ^= hash >> 13;
  hash *= 0xc2b2ae35;  
  hash ^= hash >> 16;
  return hash % table_size;
}

/* Resize string pool hash table */
static int str_pool_resize(void) {
  MSize old_size = str_pool.size;
  SharedStrData **old_buckets = str_pool.buckets;
  
  MSize new_size = old_size * 2;
  SharedStrData **new_buckets = calloc(new_size, sizeof(SharedStrData*));
  if (!new_buckets) {
    return 0;
  }
  
  str_pool.size = new_size;
  str_pool.mask = new_size - 1;
  str_pool.buckets = new_buckets;
  str_pool.count = 0;
  
  /* Rehash all existing entries */
  for (MSize i = 0; i < old_size; i++) {
    SharedStrData *node = old_buckets[i];
    while (node) {
      SharedStrData *next = node->next;
      
      /* Rehash this node */
      MSize new_slot = str_pool_hash(node->hash, new_size);
      node->next = new_buckets[new_slot];
      new_buckets[new_slot] = node;
      str_pool.count++;
      
      node = next;
    }
  }
  
  /* Free old bucket array */
  free(old_buckets);
  return 1;
}

void lj_str_pool_init(void) {
  PRNGState prng;
  lj_prng_seed_secure(&prng);
  str_pool.seed = lj_prng_u64(&prng);
  str_pool.size = STR_POOL_MIN_SIZE;
  str_pool.mask = STR_POOL_MIN_SIZE - 1;
  str_pool.count = 0;
  str_pool.buckets = calloc(STR_POOL_MIN_SIZE, sizeof(SharedStrData*));
  str_pool.next_sid = 1;
}

void lj_str_pool_free(void) {
  if (!str_pool.initialized) return;
  
  str_pool_lock();
  for (MSize i = 0; i <= str_pool.mask; i++) {
    SharedStrData *node = str_pool.buckets[i];
    while (node) {
      SharedStrData *next = node->next;
      free(node);
      node = next;
    }
  }
  free(str_pool.buckets);
  str_pool.buckets = NULL;
  
  str_pool.initialized = 0;
  str_pool_unlock();
}

static void lj_str_pool_remove(SharedStrData *target_data) {
  if (!str_pool.initialized) return;
  
  MSize slot = str_pool_hash(target_data->hash, str_pool.size);
  
  str_pool_lock();
  
  SharedStrData **node_ptr = &str_pool.buckets[slot];
  while (*node_ptr) {
    if (*node_ptr == target_data) {
      /* Found the node, remove it */
      SharedStrData *to_remove = *node_ptr;
      *node_ptr = to_remove->next;
      free(to_remove);
      str_pool.count--; /* Update count */
      break;
    }
    node_ptr = &(*node_ptr)->next;
  }
  
  str_pool_unlock();
}

SharedStrData *lj_str_pool_get(const char *str, MSize len) {

  str_pool_lock();
  
  StrHash hash = hash_sparse(str_pool.seed, str, len);
  MSize slot = str_pool_hash(hash, str_pool.size);
  SharedStrData *node = str_pool.buckets[slot];
  while (node) {
    if (node->hash == hash && node->len == len && 
        memcmp(node->data, str, len) == 0) {
      __sync_fetch_and_add(&node->refcount, 1);
      str_pool_unlock();
      return node;
    }
    node = node->next;
  }
  
  str_pool_unlock();
  
  SharedStrData *data = malloc(sizeof(SharedStrData) + len + 1);
  
  if (!data) {
    return NULL;
  }
  
  /* Initialize data outside lock */
  data->refcount = 1;
  data->hash = hash;
  data->len = len;
  memcpy(data->data, str, len);
  data->data[len] = '\0';
  
  str_pool_lock();
  
  /* Recalculate slot in case table was resized by another thread */
  slot = str_pool_hash(hash, str_pool.size);
  
  /* Double-check: maybe another thread added it while we were preparing */
  node = str_pool.buckets[slot];
  while (node) {
    if (node->hash == hash && node->len == len && 
        memcmp(node->data, str, len) == 0) {
      /* Found it, use existing and cleanup our allocation */
      __sync_fetch_and_add(&node->refcount, 1);
      str_pool_unlock();
      free(data);
      return node;
    }
    node = node->next;
  }
  
  /* Still not found, assign sid and insert */
  data->sid = ++str_pool.next_sid;
  
  /* Check if resize is needed before inserting */
  if (str_pool.count >= str_pool.size * STR_POOL_MAX_LOAD_FACTOR) {
    if (str_pool_resize()) {
      /* Table was resized, recalculate slot */
      slot = str_pool_hash(hash, str_pool.size);
    }
    /* If resize failed, continue with current table */
  }
  
  data->next = str_pool.buckets[slot];
  str_pool.buckets[slot] = data;
  str_pool.count++; /* Update count */
  
  str_pool_unlock();
  return data;
}

void LJ_FASTCALL lj_str_init(lua_State *L)
{
  global_State *g = G(L);
  g->str.seed = lj_prng_u64(&g->prng);
  lj_str_resize(L, LJ_MIN_STRTAB-1);

  if (!str_pool.initialized) {
    lj_str_pool_init();
    str_pool.initialized = 1;
  }
}

/* ------------------------------------------------------------------------ */
/* String pool debugging and statistics functions */

LUALIB_API int lj_str_pool_stats(lua_State *L)
{
  lua_createtable(L, 0, 8);
  
  /* Get string pool statistics */
  lua_pushstring(L, "initialized");
  lua_pushboolean(L, str_pool.initialized);
  lua_settable(L, -3);
  
  lua_pushstring(L, "size");
  lua_pushinteger(L, str_pool.size);
  lua_settable(L, -3);
  
  lua_pushstring(L, "count");
  lua_pushinteger(L, str_pool.count);
  lua_settable(L, -3);
  
  lua_pushstring(L, "mask");
  lua_pushinteger(L, str_pool.mask);
  lua_settable(L, -3);
  
  lua_pushstring(L, "next_sid");
  lua_pushinteger(L, str_pool.next_sid);
  lua_settable(L, -3);
  
  /* Calculate memory usage */
  size_t hash_table_bytes = str_pool.size * sizeof(SharedStrData*);
  lua_pushstring(L, "hash_table_bytes");
  lua_pushinteger(L, hash_table_bytes);
  lua_settable(L, -3);
  
  /* Calculate load factor */
  double load_factor = str_pool.size > 0 ? (double)str_pool.count / str_pool.size : 0.0;
  lua_pushstring(L, "load_factor");
  lua_pushnumber(L, load_factor);
  lua_settable(L, -3);
  
  /* Estimate total string data bytes */
  size_t estimated_data_bytes = 0;
  if (str_pool.initialized && str_pool.buckets) {
    str_pool_lock();
    for (MSize i = 0; i <= str_pool.mask; i++) {
      SharedStrData *node = str_pool.buckets[i];
      while (node) {
        estimated_data_bytes += sizeof(SharedStrData) + node->len + 1; /* +1 for null terminator */
        node = node->next;
      }
    }
    str_pool_unlock();
  }
  lua_pushstring(L, "estimated_data_bytes");
  lua_pushinteger(L, estimated_data_bytes);
  lua_settable(L, -3);
  
  return 1;
}

LUALIB_API int lj_str_pool_dump(lua_State *L)
{
  const char *filename = luaL_optstring(L, 1, "string_pool_dump.txt");
  FILE *file = fopen(filename, "w");
  if (!file) {
    lua_pushstring(L, "Cannot open file for writing");
    lua_error(L);
  }
  
  fprintf(file, "=== LuaJIT String Pool Dump ===\n");
  fprintf(file, "Timestamp: %ld\n", (long)time(NULL));
  fprintf(file, "Initialized: %s\n", str_pool.initialized ? "Yes" : "No");
  fprintf(file, "Hash table size: %u\n", str_pool.size);
  fprintf(file, "String count: %u\n", str_pool.count);
  fprintf(file, "Hash table mask: 0x%x\n", str_pool.mask);
  fprintf(file, "Next SID: %u\n", str_pool.next_sid);
  fprintf(file, "Hash table bytes: %zu\n", str_pool.size * sizeof(SharedStrData*));
  fprintf(file, "Load factor: %.4f\n", str_pool.size > 0 ? (double)str_pool.count / str_pool.size : 0.0);
  fprintf(file, "\n");
  
  if (str_pool.initialized && str_pool.buckets) {
    str_pool_lock();
    
    size_t total_data_bytes = 0;
    size_t total_refs = 0;
    unsigned int empty_buckets = 0;
    unsigned int max_chain_length = 0;
    
    fprintf(file, "=== Bucket Details ===\n");
    for (MSize i = 0; i <= str_pool.mask; i++) {
      SharedStrData *node = str_pool.buckets[i];
      unsigned int chain_length = 0;
      
      if (!node) {
        empty_buckets++;
      } else {
        fprintf(file, "Bucket %u:\n", i);
        while (node) {
          chain_length++;
          total_data_bytes += sizeof(SharedStrData) + node->len + 1;
          total_refs += node->refcount;
          
          fprintf(file, "  SID: %u, Hash: 0x%x, Len: %u, Refs: %u, Data: \"", 
                  node->sid, node->hash, node->len, node->refcount);
          
          /* Print string data with escape sequences for special characters */
          const char *data = node->data;
          for (MSize j = 0; j < node->len && j < 100; j++) { /* Limit to 100 chars */
            unsigned char c = (unsigned char)data[j];
            if (c >= 32 && c <= 126) {
              fputc(c, file);
            } else {
              fprintf(file, "\\x%02x", c);
            }
          }
          if (node->len >= 100) {
            fprintf(file, "...");
          }
          fprintf(file, "\"\n");
          
          node = node->next;
        }
        if (chain_length > max_chain_length) {
          max_chain_length = chain_length;
        }
      }
    }
    
    fprintf(file, "\n=== Summary ===\n");
    fprintf(file, "Empty buckets: %u\n", empty_buckets);
    fprintf(file, "Max chain length: %u\n", max_chain_length);
    fprintf(file, "Total data bytes: %zu\n", total_data_bytes);
    fprintf(file, "Total references: %zu\n", total_refs);
    fprintf(file, "Average references per string: %.2f\n", 
            str_pool.count > 0 ? (double)total_refs / str_pool.count : 0.0);
    
    str_pool_unlock();
  }
  
  fclose(file);
  lua_pushstring(L, "String pool dump completed successfully");
  return 1;
}

LUALIB_API int lj_str_pool_find(lua_State *L)
{
  const char *str = luaL_checkstring(L, 1);
  MSize len = strlen(str);
  
  if (!str_pool.initialized || !str_pool.buckets) {
    lua_pushnil(L);
    return 1;
  }
  
  str_pool_lock();
  
  StrHash hash = hash_sparse(str_pool.seed, str, len);
  MSize slot = str_pool_hash(hash, str_pool.size);
  SharedStrData *node = str_pool.buckets[slot];
  
  while (node) {
    if (node->hash == hash && node->len == len && 
        memcmp(node->data, str, len) == 0) {
      /* Found the string - store values to avoid deadlock */
      uint32_t sid = node->sid;
      StrHash hash_val = node->hash;
      MSize len_val = node->len;
      uint32_t refcount = node->refcount;
      MSize slot_val = slot;
      const char *data_ptr = node->data;
      MSize data_len = node->len;
      
      str_pool_unlock();
      
      /* Now create the table safely */
      lua_createtable(L, 0, 6);
      
      lua_pushstring(L, "sid");
      lua_pushinteger(L, sid);
      lua_settable(L, -3);
      
      lua_pushstring(L, "hash");
      lua_pushinteger(L, hash_val);
      lua_settable(L, -3);
      
      lua_pushstring(L, "len");
      lua_pushinteger(L, len_val);
      lua_settable(L, -3);
      
      lua_pushstring(L, "refcount");
      lua_pushinteger(L, refcount);
      lua_settable(L, -3);
      
      lua_pushstring(L, "slot");
      lua_pushinteger(L, slot_val);
      lua_settable(L, -3);
      
      lua_pushstring(L, "data");
      lua_pushlstring(L, data_ptr, data_len);
      lua_settable(L, -3);
      
      return 1;
    }
    node = node->next;
  }
  
  str_pool_unlock();
  lua_pushnil(L);
  return 1;
}

/* Helper function to calculate GCproto size */
static size_t lj_str_proto_size(GCproto *pt)
{
  size_t size = sizeof(GCproto);
  
  /* Add size of constants array */
  size += pt->sizekgc * sizeof(GCRef);
  size += pt->sizekn * sizeof(TValue);
  
  /* Add size of bytecode */
  size += pt->sizebc * sizeof(BCIns);
  
  /* Add size of debug info */
  if (pt->sizeuv) {
    size += pt->sizeuv * sizeof(uint16_t);
  }
  
  /* Add size of upvalue info - check if pointer is valid */
  if (mref(pt->uvinfo, uint8_t)) {
    size += pt->sizeuv * sizeof(uint8_t);
  }
  
  /* Add size of variable info - check if pointer is valid */
  if (mref(pt->varinfo, uint8_t)) {
    /* Estimate variable info size based on number of upvalues */
    size += pt->sizeuv * sizeof(uint8_t);
  }
  
  /* Add size of line info - check if pointer is valid */
  if (mref(pt->lineinfo, MSize)) {
    /* Estimate line info size based on bytecode size */
    size += pt->sizebc * sizeof(MSize);
  }
  
  /* Add size of chunk name string (if not shared) */
#if LJ_GC64
  if (pt->chunkname.gcptr64 != 0) {
#else
  if (pt->chunkname.gcptr32 != 0) {
#endif
    GCstr *chunkname = proto_chunkname(pt);
    if (chunkname && !(chunkname->marked & LJ_GC_FIXED)) {
      size += sizeof(GCstr) + chunkname->len + 1;
    }
  }
  
  /* Add size of constant strings (if not shared) */
  for (MSize i = 0; i < pt->sizekgc; i++) {
    GCobj *o = proto_kgc(pt, ~(ptrdiff_t)i);
    if (o->gch.gct == ~LJ_TSTR) {
      GCstr *str = gco2str(o);
      if (!(str->marked & LJ_GC_FIXED)) {
        size += sizeof(GCstr) + str->len + 1;
      }
    }
  }
  
  return size;
}

LUALIB_API int lj_str_pool_proto_stats(lua_State *L)
{
  global_State *g = G(L);
  
  size_t total_protos = 0;
  size_t total_bytes = 0;
  size_t shared_protos = 0;
  size_t shared_bytes = 0;
  
  /* Count protos in current VM only */
  GCobj *o = gcref(g->gc.root);
  while (o) {
    if (o->gch.gct == ~LJ_TPROTO) {
      GCproto *pt = gco2pt(o);
      size_t proto_size = lj_str_proto_size(pt);
      total_protos++;
      total_bytes += proto_size;
      
      /* Check if this proto is shared (marked as fixed) */
      if (pt->marked & LJ_GC_FIXED) {
        shared_protos++;
        shared_bytes += proto_size;
      }
    }
    o = gcref(o->gch.nextgc);
  }
  
  /* Calculate statistics */
  double savings_ratio = total_protos > 0 ? (double)shared_protos / total_protos : 0.0;
  double avg_proto_size = total_protos > 0 ? (double)total_bytes / total_protos : 0.0;
  double avg_shared_size = shared_protos > 0 ? (double)shared_bytes / shared_protos : 0.0;
  
  /* Build result table */
  lua_createtable(L, 0, 7);
  
  lua_pushstring(L, "total_protos");
  lua_pushinteger(L, total_protos);
  lua_settable(L, -3);
  
  lua_pushstring(L, "total_bytes");
  lua_pushinteger(L, total_bytes);
  lua_settable(L, -3);
  
  lua_pushstring(L, "shared_protos");
  lua_pushinteger(L, shared_protos);
  lua_settable(L, -3);
  
  lua_pushstring(L, "shared_bytes");
  lua_pushinteger(L, shared_bytes);
  lua_settable(L, -3);
  
  lua_pushstring(L, "savings_ratio");
  lua_pushnumber(L, savings_ratio);
  lua_settable(L, -3);
  
  lua_pushstring(L, "avg_proto_size");
  lua_pushnumber(L, avg_proto_size);
  lua_settable(L, -3);
  
  lua_pushstring(L, "avg_shared_size");
  lua_pushnumber(L, avg_shared_size);
  lua_settable(L, -3);
  
  return 1;
}
